/* ============================================================================
   POV-QLab version 0.9

   Copyright 2013 Alex Stewart <foogod@gmail.com>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
   ============================================================================
*/

#include "transforms.inc"
#include "shapes.inc"

// ========================================================================= //
// The following are general utility routines which are not specifically tied
// to the POV-QLab environment, and may be useful in other contexts as well.
// ========================================================================= //

#macro QLabPrintString(Text, Str)
  #debug concat(Text, ": ", Str, "\n")
#end

#macro QLabPrintNumber(Text, Number)
  #debug concat(Text, ": ", str(Number, 0, -1), "\n")
#end

#macro QLabPrintVector(Text, Vector)
  #debug concat(Text, ": <", vstr(3, Vector, ", ", 0, -1), ">\n")
#end

#macro QLabPrintBool(Text, Value)
  #if (Value)
    #debug concat(Text, ": true\n")
  #else
    #debug concat(Text, ": false\n")
  #end
#end

#macro QLabFindClosestPoint(RefPoint, Candidates)
  #local closest = Candidates[0];
  #local min_dist = vlength(Candidates[0] - RefPoint);
  #for (i, 1, dimension_size(Candidates, 1) - 1)
    #local dist = vlength(Candidates[i] - RefPoint);
    #if (dist < min_dist)
      #local closest = Candidates[i];
      #local min_dist = dist;
    #end
  #end
  closest
#end

// TODO: This currently assumes the up vector is always +y.
#macro QLabCalcCameraAxes(Pos, LookAt, out_X, out_Y, out_Z)
  #declare out_Z = vnormalize(LookAt - Pos);
  #declare out_X = <out_Z.z, 0, -out_Z.x>;
  #if (!vlength(out_X))
    #declare out_X = x;
  #else
    #declare out_X = vnormalize(out_X);
  #end
  #declare out_Y = vcross(out_Z, out_X);
#end

#macro QLabTorusArc(Major, Minor, Angle)
  intersection {
    torus { Major, Minor }
    Wedge(abs(Angle))
    #if (Angle < 0)
      scale <-1, 1, 1>
    #end
  }
#end

///////////////////////////////////////////////////////////////////////////////
//
// The following three incredibly useful macros have been taken (and slightly
// altered) from an example posted by Tor Olav Kristensen:
//
//   http://news.povray.org/povray.general/message/%3C3B82F267.36412849%40hotmail.com%3E/#%3C3B82F267.36412849%40hotmail.com%3E
//
///////////////////////////////////////////////////////////////////////////////

#macro QLab3PlaneIntersection(pPlane1, vPlane1, pPlane2, vPlane2, pPlane3, vPlane3)
  ((vdot(pPlane1, vPlane1)*vcross(vPlane2, vPlane3) +
    vdot(pPlane2, vPlane2)*vcross(vPlane3, vPlane1) +
    vdot(pPlane3, vPlane3)*vcross(vPlane1, vPlane2))/
    vdot(vPlane1, vcross(vPlane2, vPlane3)))
#end

#macro QLabCircleThrough3Points(p1, p2, p3, pCenter, Radius, vPlane)
  #local v12 = p2 - p1;
  #local v13 = p3 - p1;

  #declare vPlane = vnormalize(vcross(v13, v12));
  #declare pCenter = QLab3PlaneIntersection(
                       p1, vPlane,
                       (p1 + p2)/2, v12,
                       (p1 + p3)/2, v13
                     );
  #declare Radius = vlength(p1 - pCenter);
#end

#macro QLabSphereThrough4Points(p1, p2, p3, p4, pCenter, Radius)
  #declare pCenter = QLab3PlaneIntersection(
                       (p1 + p2)/2, p2 - p1,
                       (p1 + p3)/2, p3 - p1,
                       (p1 + p4)/2, p4 - p1
                     );
  #declare Radius = vlength(p1 - pCenter);
#end

// ========================================================================= //
// Init / Setup routines for the QLab environment.
//
// Any scene files should call either QLabSetup() or QLabInit() before using
// any QLab routines.
//
// QLabSetup() will construct the standard QLab scene environment based on
// specified QLAB_* settings (set up global settings, place camera, lights,
// grids, etc).
//
// QLabInit() will perform necessary QLab initialization (global defines, etc),
// but will not place any objects in the scene (in case you want to use QLab
// macros, but want to control all scene setup yourself)
//
// (QLabSetup calls QLabInit automatically.  It does not hurt anything to call
//  QLabInit more than once)
// ========================================================================= //

#macro QLabInit()
  #ifndef (QLAB_INITIALIZED)
    #declare QLAB_INITIALIZED = true;

    // Define defaults for any parameters which haven't already been specified
    // (either prior to including us, or on the command-line).
    // I wish POV-Ray had a better way to do this...

    #ifndef (QLAB_MAX_DISTANCE)
      #declare QLAB_MAX_DISTANCE = 1e7;
    #end
    #ifndef (QLAB_ASSUMED_GAMMA)
      #declare QLAB_ASSUMED_GAMMA = 1.0;
    #end
    #ifndef (QLAB_AMBIENT_LIGHT)
      #declare QLAB_AMBIENT_LIGHT = 0.1;
    #end
    #ifndef (QLAB_CAMERA_LOOK_AT)
      #declare QLAB_CAMERA_LOOK_AT = <0, 0, 0>;
    #end
    #ifndef (QLAB_CAMERA_DISTANCE)
      #declare QLAB_CAMERA_DISTANCE = 4;
    #end
    #ifndef (QLAB_CAMERA_ANGLE)
      #declare QLAB_CAMERA_ANGLE = 0; // Will be calculated from zoom and aspect ratio if 0
    #end
    #ifndef (QLAB_CAMERA_ZOOM)
      #declare QLAB_CAMERA_ZOOM = 1.0;
    #end
    #ifndef (QLAB_ASPECT_RATIO)
      #declare QLAB_ASPECT_RATIO = image_width/image_height;
    #end

    #ifndef (QLAB_LIGHT_POS)
      #declare QLAB_LIGHT_POS = vnormalize(<-1, 1, -1>) * QLAB_MAX_DISTANCE/2;
    #end
    #ifndef (QLAB_LIGHT_COLOR)
      #declare QLAB_LIGHT_COLOR = rgb 1.0;
    #end

    #ifndef (QLAB_BACKGROUND_COLOR)
      #declare QLAB_BACKGROUND_COLOR = rgb 0.5;
    #end
    #ifndef (QLAB_BACKGROUND_REFLECTION)
      #declare QLAB_BACKGROUND_REFLECTION = QLAB_BACKGROUND_COLOR;
    #end
    #ifndef (QLAB_BACKGROUND_DISTANCE)
      #declare QLAB_BACKGROUND_DISTANCE = QLAB_MAX_DISTANCE/2;
    #end

    #ifndef (QLAB_SHOW)
      #declare QLAB_SHOW = true;
    #end
    #ifndef (QLAB_SHOW_LAB)
      #declare QLAB_SHOW_LAB = true;
    #end
    #ifndef (QLAB_SHOW_HUD)
      #declare QLAB_SHOW_HUD = true;
    #end
    #ifndef (QLAB_SHOW_BACKGROUND)
      #declare QLAB_SHOW_BACKGROUND = true;
    #end
    #ifndef (QLAB_SHOW_LIGHTS)
      #declare QLAB_SHOW_LIGHTS = true;
    #end
    #ifndef (QLAB_SHOW_GRIDS)
      #declare QLAB_SHOW_GRIDS = true;
    #end
    #ifndef (QLAB_SHOW_LABELS)
      #declare QLAB_SHOW_LABELS = true;
    #end
    #ifndef (QLAB_SHOW_NOTES)
      #declare QLAB_SHOW_NOTES = true;
    #end
    #ifndef (QLAB_SHOW_ARROWS)
      #declare QLAB_SHOW_ARROWS = true;
    #end

    #ifndef (QLAB_SCALE)
      #declare QLAB_SCALE = 1.0;
    #end
    #ifndef (QLAB_LIGHT_SCALE)
      #declare QLAB_LIGHT_SCALE = 1.0;
    #end
    #ifndef (QLAB_GRID_SCALE)
      #declare QLAB_GRID_SCALE = 1.0;
    #end
    #ifndef (QLAB_ARROW_SCALE)
      #declare QLAB_ARROW_SCALE = 1.0;
    #end
    #ifndef (QLAB_LABEL_SCALE)
      #declare QLAB_LABEL_SCALE = 1.0;
    #end
    #ifndef (QLAB_CAMIND_SCALE)
      #declare QLAB_CAMIND_SCALE = 1.0;
    #end
    #ifndef (QLAB_HUD_SCALE)
      #declare QLAB_HUD_SCALE = 1.0;
    #end
    #ifndef (QLAB_HUD_LABEL_SCALE)
      #declare QLAB_HUD_LABEL_SCALE = 0.075;
    #end
    #ifndef (QLAB_HUD_ARROW_SCALE)
      #declare QLAB_HUD_ARROW_SCALE = 0.6;
    #end

    #ifndef (QLAB_HUD_LABEL_BGCOLOR)
      #declare QLAB_HUD_LABEL_BGCOLOR = rgbt 1;
    #end
    #ifndef (QLAB_LABEL_FONT)
      #declare QLAB_LABEL_FONT = "timrom.ttf";
    #end
    #ifndef (QLAB_HUD_LABEL_FONT)
      #declare QLAB_HUD_LABEL_FONT = QLAB_LABEL_FONT;
    #end

    #ifndef (QLAB_LABEL_ARROW_ANCHOR)
      #declare QLAB_LABEL_ARROW_ANCHOR = <0, 0>;
    #end

    #ifndef (QLAB_GRID_ORIGIN)
      #declare QLAB_GRID_ORIGIN = <0, 0, 0>;
    #end
    #ifndef (QLAB_GRID_LINEWIDTH)
      #declare QLAB_GRID_LINEWIDTH = 0.02;
    #end
    #ifndef (QLAB_GRID_MARKWIDTH)
      #declare QLAB_GRID_MARKWIDTH = 0.2;
    #end
    #ifndef (QLAB_GRID_COLOR)
      #declare QLAB_GRID_COLOR = rgb 0.05;
    #end
    #ifndef (QLAB_GRID_SPACING)
      #declare QLAB_GRID_SPACING = 1.0;
    #end
    #ifndef (QLAB_GRID_SPACING_MINOR)
      #declare QLAB_GRID_SPACING_MINOR = 0.2;
    #end
    #ifndef (QLAB_GRID_XZ)
      #declare QLAB_GRID_XZ = true;
    #end
    #ifndef (QLAB_GRID_XY)
      #declare QLAB_GRID_XY = false;
    #end
    #ifndef (QLAB_GRID_YZ)
      #declare QLAB_GRID_YZ = false;
    #end
    #ifndef (QLAB_GRIDLINE_X)
      #declare QLAB_GRIDLINE_X = false;
    #end
    #ifndef (QLAB_GRIDLINE_Y)
      #declare QLAB_GRIDLINE_Y = true;
    #end
    #ifndef (QLAB_GRIDLINE_Z)
      #declare QLAB_GRIDLINE_Z = false;
    #end

    #ifndef (QLAB_HUD_DISTANCE)
      #declare QLAB_HUD_DISTANCE = 0.05;
    #end
    #ifndef (QLAB_HUD_COMPASS)
      #declare QLAB_HUD_COMPASS = true;
    #end
    #ifndef (QLAB_HUD_COMPASS_LOCATION)
      #declare QLAB_HUD_COMPASS_LOCATION = <-0.8, -0.7>;
    #end

    #ifndef (QLAB_ORTHO_TOP)
      #declare QLAB_ORTHO_TOP = 0;
    #end
    #ifndef (QLAB_ORTHO_FRONT)
      #declare QLAB_ORTHO_FRONT = 0;
    #end
    #ifndef (QLAB_ORTHO_RIGHT)
      #declare QLAB_ORTHO_RIGHT = 0;
    #end
    #ifndef (QLAB_ORTHO_CAM)
      #declare QLAB_ORTHO_CAM = 0;
    #end
    #ifndef (QLAB_ORTHO_DISTANCE)
      #declare QLAB_ORTHO_DISTANCE = min(1e5, QLAB_BACKGROUND_DISTANCE * 0.9);
    #end

    // There are some colors and vectors which could be specified as
    // floats, but some places assume they're going to be vectors.  Make sure
    // everything is converted to the right form from the beginning..
    #declare QLAB_LIGHT_COLOR = color QLAB_LIGHT_COLOR * <1,1,1>;
    #declare QLAB_BACKGROUND_COLOR = color QLAB_BACKGROUND_COLOR * <1,1,1>;
    #declare QLAB_BACKGROUND_REFLECTION = color QLAB_BACKGROUND_REFLECTION * <1,1,1>;

    QLabInitCamera()
    QLabInitView()
  #end
#end

#macro QLabInitCamera()
  // If QLAB_STD_CAMERA was specified, calculate QLAB_CAMERA_POS based on that.
  // Otherwise, if QLAB_CAMERA_POS was specified, use that.
  // Otherwise, default to QLAB_STD_CAMERA=1
  #ifndef (QLAB_CAMERA_POS)
    #ifndef (QLAB_STD_CAMERA)
      #declare QLAB_STD_CAMERA = 1;
    #end
  #end
  #ifdef (QLAB_STD_CAMERA)
    #switch (QLAB_STD_CAMERA)
      #case (0)
	#declare QLAB_CAMERA_POS = <0, 0, -1>;
	#break
      #case (1)
	#declare QLAB_CAMERA_POS = <1, 2, -4>;
	#break
      #case (2)
	#declare QLAB_CAMERA_POS = <4, 2, -2>;
	#break
      #case (3)
	#declare QLAB_CAMERA_POS = <4, 2, 2>;
	#break
      #case (4)
	#declare QLAB_CAMERA_POS = <1, 2, 4>;
	#break
      #case (5)
	#declare QLAB_CAMERA_POS = <-1, 2, 4>;
	#break
      #case (6)
	#declare QLAB_CAMERA_POS = <-4, 2, 2>;
	#break
      #case (7)
	#declare QLAB_CAMERA_POS = <-4, 2, -2>;
	#break
      #case (8)
	#declare QLAB_CAMERA_POS = <-1, 2, -4>;
	#break
      #case (9)
	#declare QLAB_CAMERA_POS = <0, 0, -1>;
	#break
      #case (10)
	#declare QLAB_CAMERA_POS = <0, 1, 0>;
	#break
      #case (11)
	#declare QLAB_CAMERA_POS = <1, 0, 0>;
	#break
      #case (-1)
	#declare QLAB_CAMERA_POS = <1, -2, -4>;
	#break
      #case (-2)
	#declare QLAB_CAMERA_POS = <4, -2, -2>;
	#break
      #case (-3)
	#declare QLAB_CAMERA_POS = <4, -2, 2>;
	#break
      #case (-4)
	#declare QLAB_CAMERA_POS = <1, -2, 4>;
	#break
      #case (-5)
	#declare QLAB_CAMERA_POS = <-1, -2, 4>;
	#break
      #case (-6)
	#declare QLAB_CAMERA_POS = <-4, -2, 2>;
	#break
      #case (-7)
	#declare QLAB_CAMERA_POS = <-4, -2, -2>;
	#break
      #case (-8)
	#declare QLAB_CAMERA_POS = <-1, -2, -4>;
	#break
      #case (-9)
	#declare QLAB_CAMERA_POS = <0, 0, 1>;
	#break
      #case (-10)
	#declare QLAB_CAMERA_POS = <0, -1, 0>;
	#break
      #case (-11)
	#declare QLAB_CAMERA_POS = <-1, 0, 0>;
	#break
      #else
	#declare QLAB_CAMERA_POS = <0, 0, -1>;
    #end
    #declare QLAB_CAMERA_POS = QLAB_CAMERA_LOOK_AT + vnormalize(QLAB_CAMERA_POS) * QLAB_CAMERA_DISTANCE;
  #end

  // Calculate the other camera-related derived values
  #if (QLAB_CAMERA_ANGLE)
    #declare QLAB_CAMERA_ZOOM = (QLAB_ASPECT_RATIO / tan(radians(QLAB_CAMERA_ANGLE/2))) / 2;
  #else
    #declare QLAB_CAMERA_ANGLE = degrees(atan(QLAB_ASPECT_RATIO / (2 * QLAB_CAMERA_ZOOM))) * 2;
  #end
  #declare QLAB_CAMERA_ANGLE_V = degrees(atan(1 / (2 * QLAB_CAMERA_ZOOM))) *2;
  #declare QLAB_CAMERA_X = x;
  #declare QLAB_CAMERA_Y = y;
  #declare QLAB_CAMERA_Z = z;
  QLabCalcCameraAxes(QLAB_CAMERA_POS, QLAB_CAMERA_LOOK_AT, QLAB_CAMERA_X, QLAB_CAMERA_Y, QLAB_CAMERA_Z)
#end

#macro QLabInitView()
  // Default (perspective view) settings.  May be overridden by ortho mode
  // config below.
  #ifndef (QLAB_VIEW_POS)
    #declare QLAB_VIEW_POS = QLAB_CAMERA_POS;
  #end
  #ifndef (QLAB_VIEW_LOOK_AT)
    #declare QLAB_VIEW_LOOK_AT = QLAB_CAMERA_LOOK_AT;
  #end
  #declare QLAB_ORTHO_MODE = false;
  #declare QLAB_VIEW_DEPTH = vlength(QLAB_VIEW_LOOK_AT - QLAB_VIEW_POS);
  #declare QLAB_VIEW_WIDTH = 2 * tan(radians(QLAB_CAMERA_ANGLE/2)) * QLAB_VIEW_DEPTH;
  #declare QLAB_VIEW_HEIGHT = 2 * tan(radians(QLAB_CAMERA_ANGLE_V/2)) * QLAB_VIEW_DEPTH;
  #local ShowCamera = false;

  #if (QLAB_ORTHO_TOP)
    #declare QLAB_ORTHO_MODE = true;
    #local OrthoScale = QLAB_ORTHO_TOP;
    #local OrthoDir = y;
    #local ShowCamera = true;
  #end
  #if (QLAB_ORTHO_FRONT)
    #declare QLAB_ORTHO_MODE = true;
    #local OrthoScale = QLAB_ORTHO_FRONT;
    #local OrthoDir = -z;
    #local ShowCamera = true;
  #end
  #if (QLAB_ORTHO_RIGHT)
    #declare QLAB_ORTHO_MODE = true;
    #local OrthoScale = QLAB_ORTHO_RIGHT;
    #local OrthoDir = x;
    #local ShowCamera = true;
  #end
  #if (QLAB_ORTHO_CAM)
    // Orthographic view from the point of view of the perspective camera
    #declare QLAB_ORTHO_MODE = true;
    #local OrthoScale = QLAB_VIEW_HEIGHT * QLAB_ORTHO_CAM;
    #local OrthoDir = vnormalize(QLAB_VIEW_POS - QLAB_VIEW_LOOK_AT);
  #end
  #if (QLAB_ORTHO_MODE)
    #declare QLAB_VIEW_HEIGHT = abs(OrthoScale);
    #declare QLAB_VIEW_WIDTH = abs(OrthoScale) * QLAB_ASPECT_RATIO;
    #if (OrthoScale > 0)
      #declare QLAB_VIEW_POS = QLAB_VIEW_LOOK_AT + QLAB_ORTHO_DISTANCE * OrthoDir;
    #else
      #declare QLAB_VIEW_POS = QLAB_VIEW_LOOK_AT + QLAB_ORTHO_DISTANCE * -OrthoDir;
    #end
  #end

  #ifndef (QLAB_SHOW_CAMIND)
    #declare QLAB_SHOW_CAMIND = ShowCamera;
  #end

  // Recalculate this because our actual QLAB_VIEW_POS may have changed.
  #declare QLAB_VIEW_DEPTH = vlength(QLAB_VIEW_LOOK_AT - QLAB_VIEW_POS);

  #declare QLAB_VIEW_X = x;
  #declare QLAB_VIEW_Y = y;
  #declare QLAB_VIEW_Z = z;
  QLabCalcCameraAxes(QLAB_VIEW_POS, QLAB_VIEW_LOOK_AT, QLAB_VIEW_X, QLAB_VIEW_Y, QLAB_VIEW_Z)
#end

#macro QLabPlaceCamera()
  #if (QLAB_ORTHO_MODE)
    camera {
      orthographic
      location QLAB_VIEW_POS
      direction QLAB_VIEW_Z
      up QLAB_VIEW_HEIGHT * QLAB_VIEW_Y
      right QLAB_VIEW_WIDTH * QLAB_VIEW_X
    }
  #else
    // We're using the normal perspective camera
    camera {
      location QLAB_VIEW_POS
      up y
      right QLAB_VIEW_WIDTH / QLAB_VIEW_HEIGHT * x
      direction QLAB_CAMERA_ZOOM * z
      look_at QLAB_VIEW_LOOK_AT
    }
  #end

  #if (QLAB_SHOW_CAMIND)
    QLabPlaceCameraIndicator(QLAB_CAMERA_POS, QLAB_CAMERA_LOOK_AT)
  #end
#end

#macro QLabPrintInfo()
  QLabPrintNumber("POV-QLab: Aspect Ratio", QLAB_ASPECT_RATIO)
  QLabPrintString("POV-QLab: Camera Angle (HxV)", concat(str(QLAB_CAMERA_ANGLE, 0, 1), " x ", str(QLAB_CAMERA_ANGLE_V, 0, 1)))
  QLabPrintNumber("POV-QLab: Camera Zoom", QLAB_CAMERA_ZOOM)

  QLabPrintBool("POV-QLab: Orthographic view", QLAB_ORTHO_MODE)
  QLabPrintVector("POV-QLab: Viewport X", QLAB_VIEW_X)
  QLabPrintVector("POV-QLab: Viewport Y", QLAB_VIEW_Y)
  QLabPrintVector("POV-QLab: Viewport Z", QLAB_VIEW_Z)

  QLabPrintNumber("POV-QLab: Viewport width", QLAB_VIEW_WIDTH)
  QLabPrintNumber("POV-QLab: Viewport height", QLAB_VIEW_HEIGHT)
  QLabPrintNumber("POV-QLab: Viewport depth", QLAB_VIEW_DEPTH)
#end

#macro QLabSetup()
  QLabInit()
  #default {
    pigment { rgb 0.5 }
    finish { diffuse 0.6 ambient 0.6 }
  }
  global_settings {
    assumed_gamma QLAB_ASSUMED_GAMMA
    ambient_light QLAB_AMBIENT_LIGHT
  }
  QLabPlaceCamera()
  #if (QLAB_SHOW_BACKGROUND)
    #if (VEq(QLAB_BACKGROUND_REFLECTION, QLAB_BACKGROUND_COLOR))
      background { QLAB_BACKGROUND_COLOR }
    #else
      background { QLAB_BACKGROUND_REFLECTION }
      sphere {
        0, QLAB_BACKGROUND_DISTANCE
        pigment { QLAB_BACKGROUND_COLOR }
        finish { ambient 0 diffuse 0 emission 1 }
        hollow
        no_shadow
        no_reflection
      }
    #end
  #end
  #if (QLAB_LIGHT_COLOR.gray)
    QLabPlaceLight(QLAB_LIGHT_POS, QLAB_LIGHT_COLOR, false)
  #end
  #if (QLAB_GRID_XZ)
    QLabGridPlane(y, QLAB_GRID_ORIGIN, QLAB_GRID_COLOR)
  #end
  #if (QLAB_GRID_XY)
    QLabGridPlane(z, QLAB_GRID_ORIGIN, QLAB_GRID_COLOR)
  #end
  #if (QLAB_GRID_YZ)
    QLabGridPlane(x, QLAB_GRID_ORIGIN, QLAB_GRID_COLOR)
  #end
  #if (QLAB_GRIDLINE_X)
    QLabGridLine(x, QLAB_GRID_ORIGIN, QLAB_GRID_COLOR)
  #end
  #if (QLAB_GRIDLINE_Y)
    QLabGridLine(y, QLAB_GRID_ORIGIN, QLAB_GRID_COLOR)
  #end
  #if (QLAB_GRIDLINE_Z)
    QLabGridLine(z, QLAB_GRID_ORIGIN, QLAB_GRID_COLOR)
  #end
  #if (QLAB_HUD_COMPASS)
    QLabHUDCompass(0.2, QLAB_HUD_COMPASS_LOCATION)
  #end

  QLabPrintInfo()
#end

// ========================================================================= //
// Utility macros called by other macros in this file.  These are not generally
// intended to be used externally (but might be useful in some unusual
// circumstances).
// ========================================================================= //

#macro QLabRotateToCamera()
  Shear_Trans(QLAB_CAMERA_X, QLAB_CAMERA_Y, QLAB_CAMERA_Z)
#end

#macro QLabRotateToViewport()
  Shear_Trans(QLAB_VIEW_X, QLAB_VIEW_Y, QLAB_VIEW_Z)
#end

#macro QLabLightScale()
  (QLAB_SCALE * QLAB_LIGHT_SCALE)
#end

#macro QLabGridScale()
  (QLAB_SCALE * QLAB_GRID_SCALE)
#end
  
#macro QLabArrowScale()
  (QLAB_SCALE * QLAB_ARROW_SCALE)
#end

#macro QLabLabelScale()
  (QLAB_SCALE * QLAB_LABEL_SCALE)
#end

#macro QLabHUDArrowScale()
  (QLAB_HUD_SCALE * QLAB_HUD_ARROW_SCALE)
#end

#macro QLabHUDLabelScale()
  (QLAB_HUD_SCALE * QLAB_HUD_LABEL_SCALE)
#end

#macro QLabCameraIndicatorScale()
  (QLAB_SCALE * QLAB_CAMIND_SCALE)
#end

#macro QLabInvisibleObject()
  no_image
  no_shadow
  no_reflection
  hollow
#end

#macro QLabNonInteractingObject()
  no_shadow
  no_reflection
  hollow
#end

#macro QLabSceneObject()
  #if (!QLAB_SHOW)
    QLabInvisibleObject()
  #end
#end

#macro QLabLabObject()
  #if (!QLAB_SHOW | !QLAB_SHOW_LAB)
    QLabInvisibleObject()
  #else
    QLabNonInteractingObject()
  #end
#end

#macro QLabHUDObject()
  #if (!QLAB_SHOW | !QLAB_SHOW_HUD)
    QLabInvisibleObject()
  #else
    QLabNonInteractingObject()
  #end
#end

#macro QLabHUDScaleVector()
  #if (QLAB_ORTHO_MODE)
    #local HudScale = <(QLAB_VIEW_WIDTH/2), (QLAB_VIEW_HEIGHT/2), QLAB_HUD_DISTANCE + QLAB_VIEW_HEIGHT/2>;
  #else
    #local HudScale = <(QLAB_VIEW_WIDTH/2) / QLAB_VIEW_DEPTH, (QLAB_VIEW_HEIGHT/2) / QLAB_VIEW_DEPTH, 1> * QLAB_HUD_DISTANCE;
  #end
  HudScale
#end

#macro _LabGridPigmentDesc(LineWidth, Spacing, Color)
  #local ScaledLineWidth = (LineWidth) * QLabGridScale() / Spacing;
    boxed scale 0.5 translate <0.5,0.5,0>
    warp {repeat x} warp {repeat y} warp {planar z, 0}
    scale Spacing
    color_map {
      [0 color Color]
      [ScaledLineWidth color Color]
      [ScaledLineWidth * 1.5 color rgbt <Color.red, Color.green, Color.blue, 1>]  // transparent between lines
    }
#end

#macro QLabCameraIndicator()
  union {
    merge {
      box { <-0.5, -0.5, -0.5>, <0.5, 0.5, 0.5> }
      prism {
        conic_sweep
        linear_spline
        0, 1, // From/to Y
        5, <-1, -1>, <1, -1>, <1, 1>, <-1, 1>, <-1, -1> // Base shape
        scale <0.4, 1.2, 0.4>
      }
      pigment { rgbt <0, 0, 0, 0.2> }
      scale 0.85
      translate 0.2 * -y
      rotate 90 * x
    }
    sphere {
      0, 1.0
      pigment { rgbt <1, 1, 1, 0.8> }
      finish { diffuse 0.5 ambient 0 specular 0.25 roughness 0.005 }
    }
    scale QLabCameraIndicatorScale() * 0.5
    QLabLabObject()
  }
#end

#macro QLabLightBall(Color)
  #local Color = color Color;
  sphere {
    <0, 0, 0>, 1.0
    QLabLabObject()
    hollow
    pigment { rgbt 1 }
    finish { ambient 0 diffuse 0 }
    //pigment { rgbf <1, 1, 1, 0.9> }
    //finish { diffuse 0.8 ambient 0 specular 0.25 roughness 0.005 }
    interior {
      media {
        emission 10
        density { spherical scale 0.5 }
        density { spherical scale 0.5 }
      }
      media {
        emission 10
        density {
          spherical
          density_map {
            [0 rgb 0]
            [1.0 Color/Color.gray]
          }
        }
        density { spherical }
      }
      media {
        absorption 7.5
        density { spherical scale 0.75
          density_map {
            [0.0 rgb 0]
            [0.5 rgb 0.6]
            [0.75 rgb 1]
            [1.0 rgb 0]
          }
        }
      }
    }
    scale 0.2 * max(Color.red, Color.green, Color.blue) * QLabLightScale()
  }
#end

// ========================================================================= //
// Public Lab/Scene Object Macros (and related routines)
// ========================================================================= //

#macro QLabGridPlane(Normal, Origin, Color)
  #if (QLAB_ORTHO_MODE)
    #local CamV = QLAB_VIEW_Z;
  #else
    #local CamV = Origin - QLAB_VIEW_POS;
  #end
  // If the plane is going straight through the camera position, don't bother
  // actually drawing it, as it may cause rendering problems and is almost
  // certainly not useful.
  #if (VCos_Angle(Normal, CamV) != 0)
    plane { z, 0
      QLabLabObject()
      hollow
      #if (QLAB_GRID_SPACING_MINOR)
	texture {
	  pigment {
	    _LabGridPigmentDesc(QLAB_GRID_LINEWIDTH/2, QLAB_GRID_SPACING_MINOR, (Color*2 + QLAB_BACKGROUND_COLOR)/3)
	  }
	  finish { diffuse 0 ambient 0 emission 1 }
	}
      #end
      texture {
        pigment {
          _LabGridPigmentDesc(QLAB_GRID_LINEWIDTH, QLAB_GRID_SPACING, Color)
        }
        finish { diffuse 0 ambient 0 emission 1 }
      }
      Reorient_Trans(z, Normal)
      translate Origin
      #if (!QLAB_SHOW_GRIDS)
        no_image
      #end
    }
  #end
#end

#macro QLabGridLine(Direction, Origin, Color)
  #if (QLAB_ORTHO_MODE)
    #local CamV = QLAB_VIEW_Z;
  #else
    #local CamV = Origin - QLAB_VIEW_POS;
  #end
  // If the Direction vector is pointing straight at (or away from) the
  // camera, don't bother actually drawing it, as it may cause rendering
  // problems and is almost certainly not useful.
  #if (vlength(vcross(Direction, CamV)))
    #local sY = vnormalize(Direction);
    #local sX = VPerp_To_Plane(-CamV, sY);
    #local sZ = vcross(sX, sY);

    #local MarkWidth_Major = QLAB_GRID_MARKWIDTH * QLabGridScale() / 2;
    #local MarkWidth_Minor = (QLAB_GRID_MARKWIDTH/2) * QLabGridScale() / 2;
    #local Pigment_Grid_Major = pigment { _LabGridPigmentDesc(QLAB_GRID_LINEWIDTH, QLAB_GRID_SPACING, Color) }
    #local Pigment_Grid_Minor = pigment { _LabGridPigmentDesc(QLAB_GRID_LINEWIDTH/4, QLAB_GRID_SPACING_MINOR, Color) }
    #local Pigment_Trans = pigment { rgbt 1 }

    plane { z, 0
      QLabLabObject()
      hollow
      #if (QLAB_GRID_SPACING_MINOR)
	texture {
	  pigment {
	    cylindrical
	    scale MarkWidth_Minor
	    pigment_map {
	      [0 Pigment_Trans]
	      [0.01 Pigment_Grid_Minor scale 1/MarkWidth_Minor]
	    }
	  }
	  finish { diffuse 0 ambient 0 emission 1 }
	}
      #end
      texture {
        pigment {
          cylindrical
          scale MarkWidth_Major
          pigment_map {
            [0 Pigment_Trans]
            [0.01 Pigment_Grid_Major scale 1/MarkWidth_Major]
          }
        }
        finish { diffuse 0 ambient 0 emission 1 }
      }
      Shear_Trans(sX, sY, sZ)
      translate Origin
      #if (!QLAB_SHOW_GRIDS)
        no_image
      #end
    }
  #end
#end

#macro QLabArrow(From, To, Color)
  #local V = (To - From) / QLabArrowScale();
  #local stem_len = vlength(V) - 0.25;
  #if (stem_len >= 0.25)
    #local cap_pos = stem_len / vlength(V);
  #else
    #local cap_pos = 0.5;
  #end
  union {
    cylinder { <0, 0, 0>, V * cap_pos, 0.0375 }
    cone { V * cap_pos, 0.075, V, 0 }
    pigment { Color }
    finish { ambient 0.5 diffuse 0.5 specular 0.1 roughness 0.03 }
    scale QLabArrowScale()
    translate From
    #if (QLAB_SHOW_ARROWS)
      QLabSceneObject()
    #else
      QLabInvisibleObject()
    #end
  }
#end

#macro QLabCurvedArrow(From, Through, To, Color)
  #if (!vlength(vcross(To - From, Through - From)))
    // The points are all in a line!  Fall back to a regular QLabArrow instead.
    #warning concat("QLabCurvedArrow: Supplied points are in a straight line: <", vstr(3, From, ", ", 0, -1), ">, <", vstr(3, Through, ", ", 0, -1), ">, <", vstr(3, To, ", ", 0, -1), ">\n")
    QLabArrow(From, To, Color)
  #else
    #local Normal = 0;
    #local Center = 0;
    #local Radius = 0;
    QLabCircleThrough3Points(From, Through, To, Center, Radius, Normal)
    #local ArrowScale = QLabArrowScale();
    #local ArrowWidth = 0.0375 * ArrowScale;
    #local ArrowHeadLen = 0.25 * ArrowScale;
    #local ArrowHeadWidth = 0.075 * ArrowScale;

    #if (Radius * pi < ArrowHeadLen)
      // The arc is so small that the cap will take up more than half of it.
      // Just fall back to a regular QLabArrow, as that way we can avoid some
      // annoying special cases and nobody will really be able to tell the
      // difference anyway.
      QLabArrow(From, To, Color)
    #else
      #local Trans_Y = vnormalize(Normal);
      #local Trans_Z = vnormalize(From - Center);
      #local Trans_X = vcross(Trans_Y, Trans_Z);
      #local Angle = VRotationD(From - Center, To - Center, Normal);
      #local Arc = abs(Angle);
      #local ArcDir = sgn(Angle);
      #local Angle0 = VRotationD(From - Center, Through - Center, Normal);
      #if ((sgn(Angle) != sgn(Angle0)) | (abs(Angle) < abs(Angle0)))
	// If Angle is shorter than Angle0, or it's going the other direction,
	// it means that in order to go "through" the middle point, we need to
	// go around the circle the other direction.
        #local Arc = (360 - Arc);
        #local ArcDir = -ArcDir;
        #local Angle = Arc * ArcDir;
      #end
      #local HeadArc = Arc - ArrowHeadLen * (180 / (pi * Radius));
      #local ShaftEndArc = Arc - ArrowHeadLen * (180 / (pi * (Radius + ArrowWidth)));
      #local HeadTip = Radius * <sin(radians(Angle)), 0, cos(radians(Angle))>;
      #local HeadBase = Radius * <sin(radians(HeadArc * ArcDir)), 0, cos(radians(HeadArc * ArcDir))>;

      union {
	QLabTorusArc(Radius, ArrowWidth, ShaftEndArc * ArcDir)
        cone { HeadBase, ArrowHeadWidth, HeadTip, 0 }
	pigment { Color }
        Shear_Trans(Trans_X, Trans_Y, Trans_Z)
	translate Center
	#if (QLAB_SHOW_ARROWS)
	  QLabSceneObject()
	#else
	  QLabInvisibleObject()
	#end
      }
    #end
  #end
#end

#macro QLabLabel(Pos, Text, Color, Offset, SceneObject)
  #local LabelObj = text {
    ttf QLAB_LABEL_FONT Text 0.1, 0
  }
  #local LabelDim = max_extent(LabelObj) - min_extent(LabelObj);
  object {
    LabelObj
    pigment { Color }
    #if (!SceneObject)
      finish { diffuse 0 ambient 0 emission 0.5 }
      no_shadow
    #end
    Center_Trans(LabelObj, x+y+z)
    translate Offset * LabelDim / 2 * <1, 1, 0>
    scale 0.25 * QLabLabelScale()
    QLabRotateToCamera()
    #local DepthAdjust = (Offset * <0, 0, 1>).z;
    #if (QLAB_ORTHO_MODE)
      translate DepthAdjust * QLAB_VIEW_Z
    #else
      scale (1 + DepthAdjust / vlength(Pos - QLAB_VIEW_POS))
      translate DepthAdjust * vnormalize(Pos - QLAB_VIEW_POS)
    #end
    translate Pos
    #if (QLAB_SHOW_LABELS)
      QLabSceneObject()
    #else
      QLabInvisibleObject()
    #end
  }
#end

#macro QLabLabelledVector(V, Color, Text, LabelOffset)
  union {
    QLabArrow(0, V, Color)
    QLabLabel(V + vnormalize(V) * (0.15 + LabelOffset) * QLabLabelScale(), Text, Color * 0.5, <0, 0, 0>, 1.0)
  }
#end

#macro QLabAxisArrows(X, Y, Z, DoLabels)
  #if (DoLabels)
    union {
      QLabLabelledVector(X, rgb <0, 0, 1>, "X", 0)
      QLabLabelledVector(Y, rgb <0, 1, 0>, "Y", 0)
      QLabLabelledVector(Z, rgb <1, 0, 0>, "Z", 0)
    }
  #else
    union {
      QLabArrow(0, X, rgb <0, 0, 1>)
      QLabArrow(0, Y, rgb <0, 1, 0>)
      QLabArrow(0, Z, rgb <1, 0, 0>)
    }
  #end
#end

#macro QLabPlaceLight(Pos, Color, Fade)
  light_source {
    Pos, Color
    #if (Fade)
      fade_distance Fade
      fade_power 2
    #end
    #if (QLAB_SHOW_LIGHTS)
      looks_like { QLabLightBall(Color) }
    #end
  }
#end

#macro QLabPlaceCameraIndicator(Pos, LookAt)
  #local QLAB_CAMERA_X = x;
  #local QLAB_CAMERA_Y = y;
  #local QLAB_CAMERA_Z = z;
  QLabCalcCameraAxes(Pos, LookAt, QLAB_CAMERA_X, QLAB_CAMERA_Y, QLAB_CAMERA_Z)
  object {
    QLabCameraIndicator()
    QLabRotateToCamera()
    translate Pos
  }
#end

// ========================================================================= //
// HUD Display routines/objects
// ========================================================================= //

// We want to simulate a "Heads Up Display" on top of the rest of the scene.
// Ideally, the best way to do this would be to render the scene with the
// normal perspective camera, and then render the HUD elements with an
// orthographic camera on top of the same image, but POV-Ray doesn't support
// that.
// Alternately, we could apply an inverse-perspective transform to these
// objects to make them appear orthographic in the perspective view, but that
// would require a non-linear transform, which POV-Ray also doesn't support
// doing.
//
// What we actually do is the following:
//   1. Pretending that the camera is at the origin, pointing in the Z
//      direction, translate the object to the correct location to make it show
//      up where we want it in the viewport, positioned very close to the
//      camera (to be in front of everything else).
//   2. Apply a shear transform to compensate for the perspective effect.
//      (This is not as technically correct as a full perspective transform,
//      but can achieve close results so long as the object isn't too large,
//      and it is a linear transform)
//   3. Rotate the object to have the same orientation as the camera
//   4. Translate <0, 0, 0> to the camera's actual position.
//
// Scale is a scaling factor to apply: When Scale = 1, a 1-unit-high object
// will occupy 1 V-unit (half the height of the image) when in the HUD.
//
// PosUV is an X/Y coordinate relative to the size of the viewport, ranging
// from <-1, -1> (lower-left corner) to <1, 1> (upper-right corner)

#macro QLabPlaceOnHUD(Object, Scale, PosUV)
  #local HudScale = QLabHUDScaleVector();
  light_group {
    light_source { <-2, 2, -1> * 100, rgb 1 }
    object {
      Object
      QLabHUDObject()
      #if (!QLAB_ORTHO_MODE)
        Shear_Trans(x, y, vnormalize(<PosUV.u, PosUV.v, 1> * HudScale))
      #end
      scale Scale * HudScale.y
      translate <PosUV.u, PosUV.v, 1> * HudScale
    }
    QLabRotateToViewport()
    translate QLAB_VIEW_POS
  }
#end

#macro QLabCoordsToHUD(Pos)
  #local PerpPos = VProject_Plane(Pos - QLAB_VIEW_LOOK_AT, QLAB_VIEW_Z);
  #local PosU = vdot(PerpPos, QLAB_VIEW_X) * 2 / QLAB_VIEW_WIDTH;
  #local PosV = vdot(PerpPos, QLAB_VIEW_Y) * 2 / QLAB_VIEW_HEIGHT;
  #if (!QLAB_ORTHO_MODE)
    #local PosVZ = QLAB_VIEW_DEPTH + vdot(Pos, QLAB_VIEW_Z);
    #if (PosVZ)
      #local PerspScale = QLAB_VIEW_DEPTH / PosVZ;
      #local PosU = PosU * PerspScale;
      #local PosV = PosV * PerspScale;
    #end
  #end
  <PosU, PosV>
#end

#macro QLabHUDLabel(PosUV, Text, Color, Offset)
  #local LabelObj = text {
    ttf QLAB_HUD_LABEL_FONT Text 0.1, 0
    pigment { Color }
  }
  #local LabelDim = max_extent(LabelObj) - min_extent(LabelObj);
  #local BoxMargin = LabelDim.y / 5;
  #local BoxDim = <LabelDim.x + BoxMargin, LabelDim.y + BoxMargin, 0.05>;
  #declare mlab_tmp_label_dim = LabelDim;
  #local Object = union {
    box { 0, BoxDim pigment { QLAB_HUD_LABEL_BGCOLOR } translate -BoxDim/2 }
    object { LabelObj Center_Trans(LabelObj, x+y+z) }
    translate <Offset.u, Offset.v, -2> * LabelDim / 2
  }
  #if (QLAB_SHOW_LABELS)
    QLabPlaceOnHUD(Object, QLabHUDLabelScale(), PosUV)
  #end
#end

#macro QLabHUDLabelPointer(PosUV, Text, Color, Offset, ArrowTo)
  #local QLAB_SHOW_ARROWS = QLAB_SHOW_LABELS;

  QLabHUDLabel(PosUV, Text, Color, Offset)
  #local HudScale = QLabHUDScaleVector();
  #local HudRatio = HudScale / HudScale.y;
  #local LabelDimUV = mlab_tmp_label_dim * QLabHUDLabelScale() / HudRatio;
  #local BBoxMargin = LabelDimUV.v / 5 * 2;
  #local ArrowAnchor = QLAB_LABEL_ARROW_ANCHOR;
  #if (!vlength(ArrowAnchor))
    #local MinUV = PosUV + (-1 + Offset) * LabelDimUV / 2 - BBoxMargin/2;
    #local MaxUV = PosUV + (1 + Offset) * LabelDimUV / 2 + BBoxMargin/2;
    #local AvgUV = (MinUV + MaxUV) / 2;
    #local ToUV = QLabCoordsToHUD(ArrowTo);
    #local PossibleAnchors = array[8] {
      <MinUV.u, MaxUV.v>, <AvgUV.u, MaxUV.v>, <MaxUV.u, MaxUV.v>,
      <MinUV.u, AvgUV.v>,                     <MaxUV.u, AvgUV.v>,
      <MinUV.u, MinUV.v>, <AvgUV.u, MinUV.v>, <MaxUV.u, MinUV.v>,
    }
    #local ArrowFromUV = QLabFindClosestPoint(ToUV, PossibleAnchors);
  #else
    #local BBoxDimUV = LabelDimUV + BBoxMargin;
    #local ArrowFromUV = PosUV + Offset * LabelDimUV/2 + ArrowAnchor * BBoxDimUV/2;
  #end
  QLabHUDArrow(ArrowFromUV, ArrowTo, Color)
#end

#macro QLabHUDNote(PosUV, Text, Color, Offset)
  #local QLAB_SHOW_LABELS = QLAB_SHOW_NOTES;

  #if (QLAB_SHOW_LAB)
    QLabHUDLabel(PosUV, Text, Color, Offset)
  #end
#end

#macro QLabHUDNotePointer(PosUV, Text, Color, Offset, ArrowTo)
  #local QLAB_SHOW_LABELS = QLAB_SHOW_NOTES;

  #if (QLAB_SHOW_LAB)
    QLabHUDLabelPointer(PosUV, Text, Color, Offset, ArrowTo)
  #end
#end

#macro QLabHUDArrow(FromUV, To, Color)
  #if (QLAB_SHOW_ARROWS)
    #local QLAB_SCALE = QLAB_HUD_SCALE;
    #local QLAB_ARROW_SCALE = QLAB_HUD_ARROW_SCALE * 0.2;
    #local ToUV = QLabCoordsToHUD(To);
    #if (QLAB_ORTHO_MODE)
      #local HudScale = <(QLAB_VIEW_WIDTH/2), (QLAB_VIEW_HEIGHT/2), QLAB_HUD_DISTANCE + QLAB_VIEW_HEIGHT/2>;
      #local QLAB_ARROW_SCALE = QLAB_ARROW_SCALE * QLAB_VIEW_HEIGHT;
    #else
      #local HudScale = <(QLAB_VIEW_WIDTH/2) / QLAB_VIEW_DEPTH, (QLAB_VIEW_HEIGHT/2) / QLAB_VIEW_DEPTH, 1> * QLAB_HUD_DISTANCE;
      #local QLAB_ARROW_SCALE = QLAB_ARROW_SCALE * QLAB_HUD_DISTANCE;
    #end

    #local Object = QLabArrow(<FromUV.u - ToUV.u, FromUV.v - ToUV.v, 0> * HudScale, 0, Color);
    QLabPlaceOnHUD(Object, 1/HudScale.y, ToUV)
  #end
#end

#macro QLabHUDCompass(Scale, PosUV)
  #local QLAB_ARROW_SCALE = 1.0;
  #local QLAB_LABEL_SCALE = 1.0;
  #local QLAB_SCALE = 1.5;
  #local QLAB_CAMERA_X = QLAB_VIEW_X;
  #local QLAB_CAMERA_Y = QLAB_VIEW_Y;
  #local QLAB_CAMERA_Z = QLAB_VIEW_Z;
  #local QLAB_SHOW_ARROWS = true;
  #local QLAB_SHOW_LABELS = true;

  #local Object = union {
    object {
      QLabAxisArrows(x, y, z, true) 
      QLabLabObject()
      translate -0.2
      transform { QLabRotateToViewport() inverse }
    }
    sphere {
      0, 1.3
      QLabLabObject()
      pigment { rgbt <0.2, 0.2, 0.2, 0.9> }
      finish { diffuse 0.8 ambient 0 specular 0.25 roughness 0.005 }
    }
  }
  QLabPlaceOnHUD(Object, Scale, PosUV)
#end

////////////////////////

